package com.ynjt.queen;

import io.jenetics.*;
import io.jenetics.engine.Engine;
import io.jenetics.engine.Codecs;
import io.jenetics.engine.EvolutionResult;
import io.jenetics.engine.Limits;
import javafx.fxml.Initializable;

import javax.swing.*;
import java.awt.*;
import java.net.URL;
import java.time.Duration;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

import static io.jenetics.engine.Limits.byFitnessThreshold;

public class NQueen implements Initializable {

    private final int size;

    /**
     * with the power of the Java Parallel Stream this method will count the
     * number of queen that threat each other corisponding to the given queens
     * configuration
     *
     * @param config configuration of the queens in the chess board
     * @return total number of threat
     */
    private static long countDiagonal(final int[] config) {
        long sum = IntStream.range(0, config.length)
                .parallel()
                .mapToLong((i) -> {
                    return IntStream.range(0, config.length)
                            .parallel()
                            .filter((j) -> {
                                if (i != j) {
                                    int deltaRow = Math.abs(i - j);
                                    int deltaCol = Math.abs(config[i] - config[j]);
                                    return deltaRow == deltaCol;
                                } else {
                                    return false;
                                }
                            }).count();
                }).sum();
        return sum;
    }

    public NQueen(final int size) {
        this.size = size;

        final ExecutorService executor = Executors.newFixedThreadPool(8);

        final Engine<EnumGene<Integer>, Long> engine =
                Engine.builder(NQueen::countDiagonal, Codecs.ofPermutation(size))
                        .optimize(Optimize.MINIMUM)
                        .survivorsSelector(new TournamentSelector<>(5))
                        .offspringSelector(new LinearRankSelector<>())
                        .populationSize(100)
                        .alterers(new SwapMutator<>(0.01), new PartiallyMatchedCrossover<>(0.8))
                        .executor(executor)
                        .build();

        final Phenotype<EnumGene<Integer>, Long> best = engine.stream()
                .limit(byFitnessThreshold(1L))
                .limit(Limits.byExecutionTime(Duration.ofMinutes(30)))
                .collect(EvolutionResult.toBestPhenotype());

        executor.shutdown();
        final int[] ret = best.getGenotype().getChromosome().stream().mapToInt((gene) -> {
            return (Integer) gene.getAllele();
        }).toArray();

        final JFrame frame = new JFrame("女皇陛下万岁");
        frame.setSize(900, 900);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        final Rect r = new Rect(ret, frame.getHeight(), frame.getWidth());
        frame.add(r);
        frame.setVisible(true);

        final StringBuilder stringBuilder = new StringBuilder("Result = ");
        for (int i = 0; i < ret.length; i++) {
            stringBuilder.append("[").append(ret[i]).append("]");
        }
        System.out.println(stringBuilder.toString());
    }


    public static void main(String[] args) {
        new NQueen(8);

    }

    @Override
    public void initialize(final URL location, final ResourceBundle resources) {

    }

    class Rect extends JPanel {

        private final int[] solution;
        private final int height;
        private final int width;

        Rect(final int[] solution, final int height, final int width) {
            this.solution = solution;
            this.height = height / solution.length;
            this.width = width / solution.length;
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            for (int i = 0; i < solution.length; i++) {
                for (int j = 0; j < solution.length; j++) {
                    if ((i + 1) % 2 != 0 && (j + 1) % 2 != 0) {
                        g.drawRect(i * width, j * height, width, height);
                    } else if ((i + 1) % 2 == 0 && (j + 1) % 2 == 0) {
                        g.drawRect(i * width, j * height, width, height);
                    } else {
                        g.setColor(Color.BLACK);
                        g.fillRect(i * width, j * height, width, height);
                    }

                    if(solution[i] == j){
                        g.setColor(Color.CYAN);
                        g.fillArc(i*width, solution[i]*height, width, height, 0, 360);
                    }
                }

            }

        }
    }
}